package com.fausto.learnimprint.message.producer

import com.fausto.learnimprint.log.Slf4j.Companion.log
import com.fausto.learnimprint.log.sql.SqlLog
import com.fausto.learnimprint.message.MqConstant
import org.apache.ibatis.executor.Executor
import org.apache.ibatis.mapping.BoundSql
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.ParameterMapping
import org.apache.ibatis.plugin.Interceptor
import org.apache.ibatis.plugin.Intercepts
import org.apache.ibatis.plugin.Invocation
import org.apache.ibatis.plugin.Plugin
import org.apache.ibatis.plugin.Signature
import org.apache.ibatis.reflection.MetaObject
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.session.ResultHandler
import org.apache.ibatis.session.RowBounds
import org.apache.ibatis.type.TypeHandlerRegistry
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.text.DateFormat
import java.util.*
import java.util.regex.Matcher

@Intercepts(
    Signature(type = Executor::class, method = "update", args = [MappedStatement::class, Any::class]),
    Signature(
        type = Executor::class,
        method = "query",
        args = [MappedStatement::class, Any::class, RowBounds::class, ResultHandler::class]
    )
)
@Component
class SqlLogProducer: Interceptor{

    @Autowired
    lateinit var rabbitTemplate: RabbitTemplate

    @Throws(Throwable::class)
    override fun intercept(invocation: Invocation): Any {

        val start = System.currentTimeMillis()
        val proceed = invocation.proceed()
        val end = System.currentTimeMillis()
        val time = end - start

        //try {
        // 获取xml中的一个select/update/insert/delete节点，是一条SQL语句
        val mappedStatement: MappedStatement = invocation.args[0] as MappedStatement
        // 获取参数，if语句成立，表示sql语句有参数，参数格式是map形式
        var parameter: Any? = null
        if (invocation.args.size > 1) {
            parameter = invocation.args[1]
        }

        // BoundSql就是封装myBatis最终产生的sql类
        val boundSql = mappedStatement.getBoundSql(parameter ?: "")
        // 获取节点的配置
        val configuration: Configuration = mappedStatement.configuration

        // 获取到最终的sql语句
        val sql = showSql(configuration, boundSql)
        val logInfo = "\n{" +
                "\n\tSQL:[" + sql + "]," +
                "\n\tTime:[" + time + "ms]}"

        //sql执行耗时100ms以上时警告
        if (time > 100) {
            log.warn(logInfo)
        }

        val sqlLog = SqlLog(sql)
        sqlLog.time = time.toInt()
        sqlLog.date = Date()
        rabbitTemplate.convertAndSend(MqConstant.SQL_LOG_QUEUE, sqlLog)

//		} catch (t: Throwable) {
//			log.error("日志消息发送失败", t)
//		} catch (e: NullPointerException){
//			log.info("sql语句无参数")
//		}

        // 执行完上面的任务后，不改变原有的sql执行过程
        return proceed
    }

    override fun plugin(target: Any): Any {
        return when (target) {
            is Executor -> {
                Plugin.wrap(target, this)
            }
            else -> target
        }
    }

    override fun setProperties(properties: Properties) {}

    companion object {
        /**
         * 如果参数是String，则添加单引号，
         * 如果是日期，则转换为时间格式器并加单引号；
         * 对参数是null和不是null的情况作了处理
         */
        private fun getParameterValue(obj: Any?): String {
            val value = when (obj) {
                is String -> {
                    "'$obj'"
                }
                is Date -> {
                    val formatter: DateFormat = DateFormat.getDateTimeInstance(
                        DateFormat.DEFAULT,
                        DateFormat.DEFAULT, Locale.CHINA
                    )
                    "'" + formatter.format(Date()) + "'"
                }
                else -> {
                    obj?.toString() ?: ""
                }
            }
            return value
        }

        // 进行？的替换
        fun showSql(configuration: Configuration, boundSql: BoundSql): String {
            // 获取参数
            val parameterObject: Any = boundSql.parameterObject
            val parameterMappings: List<ParameterMapping> = boundSql.parameterMappings

            // sql语句中多个空格都用一个空格代替
            var sql: String = boundSql.sql.replace("[\\s]+".toRegex(), " ")

            // 获取类型处理器注册器，类型处理器的功能是进行java类型和数据库类型的转换
            val typeHandlerRegistry: TypeHandlerRegistry = configuration.typeHandlerRegistry

            // 如果根据parameterObject.getClass(）可以找到对应的类型，则替换
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.javaClass)) {
                sql = sql.replaceFirst(
                    "\\?".toRegex(),
                    Matcher.quoteReplacement(getParameterValue(parameterObject))
                )
            } else {
                // MetaObject主要是封装了originalObject对象，提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                val metaObject: MetaObject = configuration.newMetaObject(parameterObject)
                for (parameterMapping in parameterMappings) {
                    val propertyName: String = parameterMapping.property
                    sql = if (metaObject.hasGetter(propertyName)) {
                        val obj = metaObject.getValue(propertyName)
                        sql.replaceFirst(
                            "\\?".toRegex(),
                            Matcher.quoteReplacement(getParameterValue(obj))
                        )
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        // 该分支是动态sql
                        val obj: Any = boundSql.getAdditionalParameter(propertyName)
                        sql.replaceFirst(
                            "\\?".toRegex(),
                            Matcher.quoteReplacement(getParameterValue(obj))
                        )
                    } else {
                        // 打印出缺失，提醒该参数缺失并防止错位
                        sql.replaceFirst("\\?".toRegex(), "缺失")
                    }
                }
            }
            return sql
        }
    }
}